package mols;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.ChoiceGroup;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.ItemCommandListener;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.location.AddressInfo;
import javax.microedition.location.Coordinates;
import javax.microedition.location.LocationException;
import javax.microedition.midlet.MIDlet;
import org.json.me.JSONArray;
import org.json.me.JSONObject;

/**
 * Classe MIDlet del progetto. Mols &egrave; un acronimo per Mobile Orientated
 * Local Search. La MIDlet &egrave; una estensione del servizio di local search
 * offerto da Google, che sfrutta per il suo funzionamento le Location API
 * (JSR 179), il servizio Local Search e Google Static Map di Google. Per poter
 * funzionare, il terminale deve essere provvisto del supporto all'orientamento.
 * <br />
 * Puntando il cellulare in una direzione d, &egrave; possibile cercare dei
 * punti di interesse a x Km nella direzione specificata, e in un raggio
 * specificato. In seguito, &egrave; possibile visualizzare i risultati su 
 * una mappa, richiesta anch'essa a Google.
 * <br />
 * La MIDlet implementa tre interfacce:
 * <ul>
 *      <li><code>CommandListener</code> - per gestire i comandi che l'utente
 *          sceglie dai menu posti in basso a destra e a sinistra dello
 *          schermo;
 *      </li>
 *      <li>
 *          <code>ItemCommandListener</code> - per gestire gli eventi sollevati
 *          dai comandi posizionati sullo schermo, escludendo quelli menzionati
 *          in precedenza;
 *      </li>
 *      <li>
 *          <code>Runnable</code> - per aggiornare, in un thread separato,
 *          lo stato dell'interfaccia utente durante le fasi di connessione
 *          ai servizi di google e le elaborazioni immediatamente successive.
 *          Stessa cosa avviene durante la connessione da parte delle Location
 *          API ai servizi collegati.
 *      </li>
 * </ul>
 * 
 * @authors Matteo Mancini, Alessandro Giannini
 */
public class Mols extends MIDlet
            implements CommandListener, ItemCommandListener, Runnable {

    private Display mDisplay;
    private Command setting, exitCommand, searchPOI, back, map, go, details, 
            viewPOI, zoomInHome, zoomOutHome, zoomInPOI, zoomOutPOI, refresh,
            viewPOI2, mapLegend, viewPOI3;
    private Form form, set, search, detailsForm, mapPoi;
    private Alert alert, noResult;
    private boolean viewMap, homeMap, viewMobileAndSearchCentre, viewAll, viewMapPOI;
    private ChoiceGroup punti_interesse;
    // punti di interesse selezionati
    private String[] POISelected;
    // text field per l'inserimento dei dati
    private TextField dist, altriInteressi, raggio;
    private List lista;
    private POI poi[];
    private Coordinates c, centro;
    private String [] listPOI = {"Ristoranti","Cinema","Musei","Pub"};
    private int bestImgWidth, bestImgHeight, ni, t, zoom;
    private ImageItem myPosition;
    private StringItem viewPOIMapBtn;
    private String prevTitle;
    private Displayable prevDisplayable;

    // ---------- Costruttore e implementazione della classe MIDlet -----------
    /**
     * Il costruttore si occupa della creazione ed inizializzazione di tutti
     * gli oggetti <code>Form</code> della MIDlet. 
     */
    public Mols(){
        //Recupero il Display
        mDisplay = Display.getDisplay(this);
        
        //Inizializzazione variabili
        viewMap = false;
        homeMap = false;
        viewAll = false;
        viewMobileAndSearchCentre = false;
        viewMapPOI = false;
        c = new Coordinates(0,0,0);
        centro = new Coordinates(0, 0, Float.NaN);
        zoom = 13;
        myPosition = null;
        
        //Creo i diversi comandi per le form
        searchPOI = new Command("Cerca POI",Command.ITEM,1);
        exitCommand = new Command("Exit", Command.EXIT,0);
        setting = new Command("Impostazioni",Command.ITEM,1);
        back = new Command("Indietro",Command.ITEM,1);
        map = new Command("Home",Command.ITEM,1);
        refresh = new Command("Aggiorna Mappa", Command.ITEM, 1);
        go = new Command("Cerca",Command.ITEM,1);
        details = new Command("Dettagli",Command.ITEM,1);
        viewPOI = new Command("Mappa POI",Command.ITEM,1);
        viewPOI2 = new Command("Mappa di insieme", Command.ITEM, 1);
        viewPOI3 = new Command("Mappa di insieme", Command.ITEM, 1);
        zoomInHome = new Command("Zoom In",Command.ITEM,1);
        zoomOutHome = new Command("Zoom Out",Command.ITEM,1);
        zoomInPOI = new Command("Zoom In",Command.ITEM,1);
        zoomOutPOI = new Command("Zoom Out",Command.ITEM,1);
        mapLegend = new Command("Legenda", Command.ITEM, 1);

        //Form per la mappa
        form = new Form("Posizione attuale");
        form.addCommand(searchPOI);
        form.addCommand(setting);
        form.addCommand(refresh);
        form.addCommand(zoomInHome);
        form.addCommand(zoomOutHome);
        form.addCommand(exitCommand);
        form.setCommandListener(this);

         //Recupero dimensioni
        bestImgWidth = form.getWidth();
        bestImgHeight = form.getHeight();

        //Creo il form per le preferenze
        set = new Form("Impostazioni");
        set.addCommand(searchPOI);
        set.addCommand(exitCommand);
        set.addCommand(map);
        punti_interesse = new ChoiceGroup("Punti di Interesse:",ChoiceGroup.MULTIPLE,listPOI,null);
        punti_interesse.setSelectedFlags(new boolean[]{false,false,false,false});
        set.append(punti_interesse);
        altriInteressi = new TextField("Altro:","",20,TextField.ANY);
        set.append(altriInteressi);
        dist = new TextField("Distanza dal centro (m):\n(0 = cerca POI circostanti)","0",15,TextField.DECIMAL);
        set.append(dist);
        raggio = new TextField("Raggio di ricerca (m):\n(vuoto = raggio infinito)","10000",15,TextField.DECIMAL);
        set.append(raggio);
        set.setCommandListener(this);

        //Creo il form per impostare la direzione
        search = new Form("Ricerca POI");
        search.addCommand(exitCommand);
        search.addCommand(map);
        search.setCommandListener(this);
        StringItem s = new StringItem("","Puntare il mobile verso la direzione desiderata e premere CERCA");
        Font f=Font.getFont(Font.FACE_SYSTEM,Font.STYLE_ITALIC,Font.SIZE_LARGE);
        s.setFont(f);
        search.append(s);

        //Bottone per la ricerca dei POI
        StringItem searchBtn = new StringItem("", "Cerca", Item.BUTTON);
        searchBtn.setDefaultCommand(go);
        searchBtn.setItemCommandListener(this);
        searchBtn.setLayout(StringItem.LAYOUT_CENTER);
        search.append(searchBtn);

        //Creo lista per la visualizzazione dei POI trovati
        lista = new List("POI trovati",List.IMPLICIT);
        lista.addCommand(exitCommand);
        lista.addCommand(details);
        lista.addCommand(viewPOI3);
        lista.addCommand(map);
        lista.setCommandListener(this);

        //Creo il Form per la visualizzazione dei dettagli del POI selezionato
        detailsForm = new Form("Dettagli");
        detailsForm.addCommand(exitCommand);
        detailsForm.addCommand(back);
        detailsForm.addCommand(viewPOI2);
        detailsForm.addCommand(map);
        detailsForm.setCommandListener(this);
        viewPOIMapBtn = new StringItem("", "Visualizza sulla mappa", Item.BUTTON);
        viewPOIMapBtn.setDefaultCommand(viewPOI);
        viewPOIMapBtn.setLayout(StringItem.LAYOUT_CENTER);
        viewPOIMapBtn.setItemCommandListener(this);

        //Form per la visualizzazione della mappa
        mapPoi = new Form("Mappa POI");
        mapPoi.addCommand(mapLegend);
        mapPoi.addCommand(back);
        mapPoi.addCommand(zoomInPOI);
        mapPoi.addCommand(zoomOutPOI);
        mapPoi.addCommand(map);
        mapPoi.addCommand(exitCommand);
        mapPoi.setCommandListener(this);

        //Alert per messaggi importanti
        alert = new Alert("Attenzione!");
        alert.setTimeout(Alert.FOREVER);
        noResult = new Alert("Oh no...");
        noResult.setTimeout(Alert.FOREVER);
    }

    /**
     * Avvia la MIDlet.
     */
    public void startApp() {
        mDisplay.setCurrent(form);
        viewMap = true;
        homeMap = true;
        repaint();
    }

    /**
     * Pausa la MIDlet.
     */
    public void pauseApp() {}

    /**
     * Distrugge la MIDlet.
     */
    public void destroyApp(boolean unconditional) {
        notifyDestroyed();
    }
    // ------------------------------------------------------------------------

    // ------- Gestione degli eventi sollevati dai comandi utente -------------
    /**
     * Implementazione dell'interfaccia <code>CommandListener</code>. Questo
     * metodo gestisce gli eventi sollevati dai comandi posti nella parte
     * bassa dello schermo.
     */
    public void commandAction(Command c, Displayable d) {
         if(c.getCommandType()==Command.EXIT){
            destroyApp(true);
        }
        else if(c == setting){
            mDisplay.setCurrent(set);
        }
        else if(c == searchPOI){
            POISelected = set_POI();
            //Controllo che sia selezionato almeno un punto di interesse
            if(ni == 0 && altriInteressi.getString().equals(""))
            {
                alert.setTitle("Attenzione");
                alert.setString("Selezionare almeno un punto di interesse dalla pagina di Impostazioni");
                mDisplay.setCurrent(alert, set);
            }
            else{
                mDisplay.setCurrent(search);
            }
        }
        else if(c == map){
            viewMap = false; // non richiedo l'aggiornamento della mappa
            viewMapPOI = false;
            form.deleteAll();
            paintMe();
            form.append(myPosition);
            mDisplay.setCurrent(form);  
        }
        else if (c == refresh){
            viewMap = true;
            homeMap = true;
            repaint();
        }
        else if(c == details){
            detailsForm.deleteAll();
            t = lista.getSelectedIndex();
            this.fillDetailsForm(detailsForm, poi[t]);
            mDisplay.setCurrent(detailsForm);
        }
         else if(c == viewPOI2){
            viewMap = true;
            viewMapPOI = false;
            detailsForm.deleteAll();
            mapPoi.deleteAll();
            mapPoi.removeCommand(zoomInPOI);
            mapPoi.removeCommand(zoomOutPOI);
            t = lista.getSelectedIndex();
            mDisplay.setCurrent(mapPoi);
            viewMobileAndSearchCentre = true;
            mapPoi.setTitle("In connessione...");
            repaint();
        }
        else if(c == viewPOI3){
            viewMap = true;
            viewAll = true;
            viewMapPOI = false;
            viewMobileAndSearchCentre = true;
            mapPoi.deleteAll();
            mapPoi.removeCommand(zoomInPOI);
            mapPoi.removeCommand(zoomOutPOI);
            mDisplay.setCurrent(mapPoi);
            mapPoi.setTitle("In connessione...");
            repaint();
        }
        else if(c == zoomInPOI){
            zoom += 1;
            viewMap = true;
            mapPoi.deleteAll();
            mDisplay.setCurrent(mapPoi);
            repaint();
        }
        else if(c == zoomOutPOI){
            zoom -= 1;
            viewMap = true;
            mapPoi.deleteAll();
            mDisplay.setCurrent(mapPoi);
            repaint();
        }
        else if(c == zoomInHome){
            zoom += 1;
            viewMap = true;
            homeMap = true;
            form.deleteAll();
            repaint();
            mDisplay.setCurrent(form);
        }
        else if(c == zoomOutHome){
            zoom -= 1;
            viewMap = true;
            homeMap = true;
            form.deleteAll();
            repaint();
            mDisplay.setCurrent(form);
        }
        else if(c == back){
            mDisplay.setCurrent(lista);
        }
        else if(c == mapLegend){
            alert.setTitle("Legenda");
            alert.setString("(M): Mobile\n(C): Centro di ricerca\n"+
                    "(P): POI\n(<cifra>): indice del POI");
            mDisplay.setCurrent(alert);
        }
    }

    /**
     * Implementazione dell'interfaccia <code>ItemCommandListener</code>.
     * Questo metodo gestisce gli eventi sollevati dai comandi posizionati non
     * nella parte bassa dello schermo.
     */
    public void commandAction(Command c, Item item) {
        if (c == go){
            lista.deleteAll();
            repaint();
        } else if (c == viewPOI){
            viewMap = true;
            viewMapPOI = true;
            detailsForm.deleteAll();
            mapPoi.deleteAll();
            mapPoi.addCommand(zoomInPOI);
            mapPoi.addCommand(zoomOutPOI);
            t = lista.getSelectedIndex();
            mDisplay.setCurrent(mapPoi);
            mapPoi.setTitle("In connessione...");
            repaint();
        }
    }
    // ------------------------------------------------------------------------

    // ------------ Metodi privati della MIDlet -------------------------------
    // routine per l'esecuzione delle azioni
    private void paintMe(){

        int prevZoom = zoom;
        // Imposto le coordinate di un certo POI selezionato
        if((mDisplay.getCurrent() == detailsForm ||
                    mDisplay.getCurrent() == mapPoi) && !homeMap
                    && !viewMobileAndSearchCentre)
        {
            c.setLatitude(poi[t].getQualifiedCoordinates().getLatitude());
            c.setLongitude(poi[t].getQualifiedCoordinates().getLongitude());
        }
        // Imposto le coordinate della posizione attuale
        else
        {
            GeoUtils.getMyPosition(c);
        }
        // Richiedo e visualizzo la mappa
        if(viewMap)
        {
            Coordinates[] pointsToView = null;
            String[] labels = null;
            String[] colors = null;

            if(viewMobileAndSearchCentre){
                int l = 3;
                if (viewAll){
                    l = 0;
                    for (int i=0; i<poi.length; i++){
                        if (poi[i].isValid()) l++;
                    }
                    l = l + 2;
                }
                pointsToView = new Coordinates[l];
                labels = new String[l];
                colors = new String[l];
                pointsToView[0] = c;        // posizione mobile
                pointsToView[1] = centro;   // posizione centro di ricerca
                labels[0] = "M";
                labels[1] = "C";
                colors[0] = "green";
                colors[1] = "yellow";
                if (!viewAll){
                    pointsToView[2] = poi[t].getQualifiedCoordinates();
                    labels[2] = "P";
                    colors[2] = "red";
                } else {
                    int tmp = 2;
                    for (int i=0; i<poi.length; i++){
                        if (poi[i].isValid()){
                            pointsToView[tmp] = poi[i].getQualifiedCoordinates();
                            labels[tmp] = Integer.toString(tmp-1);
                            colors[tmp] = "red";
                            tmp++;
                        }
                    }
                }
                zoom = -1;
            } else if (viewMapPOI) {
                pointsToView = new Coordinates[1];
                labels = new String[1];
                colors = new String[1];
                pointsToView[0] = c;
                labels[0] = "P";
                colors[0] = "red";
            } else {
                pointsToView = new Coordinates[1];
                labels = new String[1];
                colors = new String[1];
                pointsToView[0] = c;
                labels[0] = "M";
                colors[0] = "green";
            }
            ImageItem googleImg = GoogleRequestStub.getStaticMap(pointsToView, 
                zoom, bestImgWidth, bestImgHeight, labels, colors);
            zoom = prevZoom;
            if (googleImg != null){
                if(homeMap)
                {
                    myPosition = googleImg;
                    form.deleteAll();
                    form.append(googleImg);
                    form.setTitle("Posizione attuale");
                } else { // Richiesta la mappa del POI selezionato
                    mapPoi.append(googleImg);
                    mapPoi.setTitle(poi[t].getName());
                }
            }
            viewMobileAndSearchCentre = false;
            viewMap = false;
            homeMap = false;
            if (viewAll){
                viewAll = false;
                mDisplay.getCurrent().setTitle("Mappa d'insieme");
            }
        }
        // Richiesta a Google Local Search dei punti di interesse selezionati
         else if (search.isShown())
        {
            StringBuffer interessi = new StringBuffer();
            for(int  i=0; i<POISelected.length; i++)
                interessi.append(POISelected[i]).append("|");
            //Aggiungo interessi inseriti dal TextField
            interessi.append(altriInteressi.getString().replace(' ', '+'));

            try {
                GeoUtils.getSearchCentre
                        (c, Double.parseDouble(dist.getString()), centro);
            } catch (LocationException ex) {}

            JSONObject googleJson = GoogleRequestStub.getPOIsFromGoogle
                    (centro, interessi.toString());

            if (googleJson != null){
                try{
                    googleJson = googleJson.getJSONObject("responseData");
                    JSONArray results = (JSONArray)googleJson.get("results");
                    poi = new POI[results.length()];
                    double radius = -1;
                    int risp = results.length();
                    StringBuffer poiDescr = new StringBuffer();
                    if(raggio.getString().equals("")){
                        radius = -1;
                    }else{
                        radius = Double.parseDouble(raggio.getString());
                    }
                    for (int i=0; i<results.length(); i++){
                        poi[i] = GeoUtils.buildPOIFromGoogleJSON
                            (results.getJSONObject(i));
                         //Controllo se il POI è nel raggio di ricerca
                        double tmpDistance = GeoUtils.isInRadius(centro, radius,
                                 poi[i].getQualifiedCoordinates());
                        if (tmpDistance != -1)
                        {
                            poi[i].setValid(true);
                            poi[i].setDistanceFromSearchCentre(tmpDistance);
                            poi[i].setDistanceFromMe(
                                GeoUtils.distanceBetween(c,
                                poi[i].getQualifiedCoordinates()));

                            poiDescr.append(i+1).append(". ");
                            poiDescr.append(poi[i].getName());
                            poiDescr.append("\n   Dal centro di ricerca: ");
                            poiDescr.append((int)tmpDistance);
                            poiDescr.append(" metri");
                            poiDescr.append("\n   Dal mobile: ");
                            poiDescr.append((int)poi[i].getDistanceFromMe());
                            poiDescr.append(" metri\n");

                            lista.append(poiDescr.toString(), null);
                            poiDescr.delete(0, poiDescr.capacity());
                            risp--;
                        }
                    }
                    //Sono stati trovati dei risultati
                    if (risp != results.length()){
                        mDisplay.setCurrent(lista);
                    }
                    else{
                        noResult.setTitle("Oh no..");
                        noResult.setString("Nussun risultato trovato con i " +
                                "parametri inseriti"+"\n"+
                                "Prova a modificare le impostazioni");
                    mDisplay.setCurrent(noResult, set);
                    }
            

                } catch(Exception e){}
            }
        }
    }

    // Metodo per impostare le preferenze
    private String [] set_POI(){
        //Recupero i soggetti selezionati
        boolean[] selected=new boolean[punti_interesse.size()];
        ni = punti_interesse.getSelectedFlags(selected);
        String []temp = new String[selected.length];
        for (int i=0;i<selected.length;i++){
            if (selected[i])        
                temp[i] = punti_interesse.getString(i);
            else 
                temp[i]="";
        }
        return temp;
    }

    private void fillDetailsForm(Form f, POI poi){
        Font boldFont = Font.getFont
                (Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM);

        String[] headers = {"Nome:", "Distanza dal centro di ricerca:", 
            "Distanza dal mobile:","Indirizzo:", "Telefono:"};
        String[] values = {poi.getName(),
            ((int)poi.getDistanceFromSearchCentre()) + " metri",
            ((int)poi.getDistanceFromMe()) + " metri",
            poi.getAddressInfo().getField(AddressInfo.STREET),
            poi.getAddressInfo().getField(AddressInfo.PHONE_NUMBER)};

        StringBuffer buf = new StringBuffer();
        StringItem tmpItem = null;

        for (int i=0; i<headers.length; i++){
            tmpItem = new StringItem("", headers[i]);
            tmpItem.setFont(boldFont);
            f.append(tmpItem);
            buf.append(values[i]).append("\n");
            f.append(buf.toString());
            buf.delete(0, buf.capacity());
        }

        detailsForm.append(viewPOIMapBtn);
    }
    // ------------------------------------------------------------------------

    // ------------------------------------------------------------------------
    // Da qui avviene la gestione dell'aggiornamento dello stato della GUI
    // in un thread separato.
    // Per avviare un'azione concorrente viene invocato il metodo repaint, che
    // a sua volta crea un nuovo thread e lo avvia. Il thread richiama sempli-
    // cemente il metodo paintMe() per eseguire, in background, le operazioni
    // di connessione. Prima di terminare, il thread aggiorna il titolo del
    // Form corrente, per resettare la stringa di connessione in corso
    // precedentemente visualizzata.
    /**
     * Avvia il thread per l'esecuzione delle azioni di rete da parte della
     * MIDlet.
     */
    public void run(){
        paintMe();
        prevDisplayable.setTitle(prevTitle);
    }

    private void repaint(){
        prevDisplayable = mDisplay.getCurrent();
        if (prevDisplayable == null) prevDisplayable = form;
        prevTitle = prevDisplayable.getTitle();
        prevDisplayable.setTitle("In connessione...");
        Thread paintThread = new Thread(this);
        paintThread.start();
    }
    // ------------------------------------------------------------------------
}
